package com.innjia.shiro.realm;

import com.innjia.base.common.Constant;
import com.innjia.base.enums.UserSysRoleEnum;
import com.innjia.base.exception.LoginException;
import com.innjia.shiro.utils.LoginToken;
import com.innjia.sys.entity.UserAdminEntity;
import com.innjia.sys.entity.UserEntity;
import com.innjia.sys.entity.UserLandlordEntity;
import com.innjia.sys.entity.UserTenantEntity;
import com.innjia.sys.service.UserAdminService;
import com.innjia.sys.service.UserLandlordService;
import com.innjia.sys.service.UserService;
import com.innjia.sys.service.UserTenantService;
import com.innjia.utils.ShiroUtils;
import com.innjia.utils.UserUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.shiro.authc.AuthenticationException;
import org.apache.shiro.authc.AuthenticationInfo;
import org.apache.shiro.authc.AuthenticationToken;
import org.apache.shiro.authc.SimpleAuthenticationInfo;
import org.apache.shiro.authc.credential.CredentialsMatcher;
import org.apache.shiro.authc.credential.HashedCredentialsMatcher;
import org.apache.shiro.util.ByteSource;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.data.redis.core.RedisTemplate;

import java.io.Serializable;
import java.util.concurrent.TimeUnit;
import java.util.function.Function;
import java.util.function.Predicate;

/**
 * 类的功能描述.
 * shiro 认证
 *
 * @author innjia
 * @Date 2017/4/27
 */
public class InnjiaRealm extends BaseRealm {

    private static final Logger LOGGER = LoggerFactory.getLogger(InnjiaRealm.class);

    @Autowired
    private RedisTemplate<String, Serializable> redisTemplate;

    @Autowired
    private UserAdminService userAdminService;

    @Autowired
    private UserLandlordService userLandlordService;

    @Autowired
    private UserTenantService userTenantService;

    /**
     * token 有效时长
     */
    @Value("${token.innjia.timeout}")
    private int timeOut;

    @Override
    public void setCredentialsMatcher(CredentialsMatcher credentialsMatcher) {
        HashedCredentialsMatcher shaCredentialsMatcher = new HashedCredentialsMatcher();
        shaCredentialsMatcher.setHashAlgorithmName(ShiroUtils.algorithmName);
        shaCredentialsMatcher.setHashIterations(ShiroUtils.hashIterations);
        super.setCredentialsMatcher(shaCredentialsMatcher);
    }

    @Override
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
        if (!(authenticationToken instanceof LoginToken)) {
            throw new AuthenticationException("不支持的登录方式");
        }
        LoginToken token = (LoginToken) authenticationToken;
        UserSysRoleEnum roleEnum = token.getUserRole();
        if (UserSysRoleEnum.ADMIN.equals(roleEnum)) {
            //管理员登录
            UserEntity user = getUserAdmin(token);
            return new SimpleAuthenticationInfo(user, user.getPassWord(), ByteSource.Util.bytes(user.getSalt()), getName());
        } else if (UserSysRoleEnum.LANDLORD.equals(roleEnum)) {
            //房东登录
            UserEntity user = getAppUser(token, userLandlordService, this::validateUser, userEntity -> userEntity instanceof UserLandlordEntity);
            return new SimpleAuthenticationInfo(user, ShiroUtils.encodeSalt(new String(token.getPassword()), null), getName());
        } else if (UserSysRoleEnum.TENANT.equals(roleEnum)) {
            //租客登录(用户不存在则新增用户)
            UserEntity user = getAppUser(token, userTenantService, userEntity -> {
                if (null != userEntity) {
                    return validateUser(userEntity);
                } else {
                    userEntity = new UserTenantEntity();
                    userEntity.setPhone(token.getMobile());
                    userTenantService.save(userEntity);
                    return userEntity;
                }
            }, userEntity -> userEntity instanceof UserTenantEntity);
            return new SimpleAuthenticationInfo(user, ShiroUtils.encodeSalt(new String(token.getPassword()), null), getName());
        } else {
            throw new LoginException("不支持的登录方式");
        }
    }

    /**
     * 管理员登录
     *
     * @param token
     * @return
     */
    private UserEntity getUserAdmin(LoginToken token) {
        String userLoginName = (String) token.getPrincipal();
        if (null == userLoginName) {
            throw new LoginException("帐号不能为空");
        }
        UserAdminEntity user = (UserAdminEntity) userAdminService.queryByLoginName(userLoginName);
        validateUser(user);
        return user;
    }

    /**
     * APP方式操作,获取用户信息(如果有token,则从token里取,如果取不到,则去查询)
     *
     * @param token
     * @param service
     * @param validUser
     *         验证查询后的用户数据(支持修改)
     * @param predicate
     *         验证查询后及token中取出的数据
     * @return
     */
    private UserEntity getAppUser(LoginToken token, UserService service, Function<UserEntity, UserEntity> validUser, Predicate<UserEntity> predicate) {
        String tokenKey = Constant.TOKEN__PREFIX + token.getToken();
        UserEntity userEntity = (UserEntity) redisTemplate.opsForValue().get(tokenKey);
        boolean isNull = (null == userEntity);
        //token 有效,而不是登录方法生成的
        if (isNull) {
            if (null != token.getUserEntity()) {
                //UID,且已有用户过来的
                userEntity = token.getUserEntity();
            } else {
                //手机,验证码登录
                //登录
                String code = getCodeString(token);
                //验证验证码
                if (!code.equals(token.getCode())) {
                    throw new LoginException("验证码有误");
                }
                userEntity = service.queryByPhone(token.getMobile());
                userEntity = validUser.apply(userEntity);
                if (StringUtils.isNotBlank(token.getUid())) {
                    //如果有uid,则需要绑定
                    UserUtils.setUid(userEntity, token.getUid(), token.getLoginSrc());
                    service.update(userEntity);
                }
            }
        }
        if (predicate.test(userEntity)) {
            if (isNull) {
                redisTemplate.opsForValue().set(tokenKey, userEntity);
            }
            //刷新有效期
            redisTemplate.expire(tokenKey, timeOut, TimeUnit.HOURS);
            return userEntity;
        } else {
            throw new LoginException("不支持的登录方式");
        }
    }

    private UserEntity validateUser(UserEntity user) {
        if (user == null) {
            throw new LoginException("账号不存在,请联系审核注册");
        }
        if (Constant.ABLE_STATUS.NO.getValue().equals(user.getStatus())) {
            throw new LoginException("帐号被禁用,请联系管理员!");
        }
        return user;
    }

    private String getCodeString(LoginToken token) {
        String codeKey = Constant.SNS__PREFIX__CODE + token.getMobile();
        String code = (String) redisTemplate.opsForValue().get(codeKey);
        if (StringUtils.isBlank(code)) {
            throw new LoginException("请重新获取验证码");
        }
        return code;
    }

}
